import { NextRequest, NextResponse } from 'next/server'; import { fetchJson } from '@/lib/utils/server'; import { isSocialLoginProvider, SOCIAL_PROVIDER_LABELS, OAUTH_STATE_COOKIE_PREFIX } from '@/constants/social'; /** * 소셜 로그인 시작 라우트 (Naver/Kakao) — /login/{provider}/start * * 1. state 생성 (crypto.randomUUID) → httpOnly 쿠키 보관 (10분) * 2. 백엔드 GET /api/auth/{provider}/authorize-url 호출 * 3. 각사 authorize 페이지로 302 리다이렉트 * * Google 은 기존 GIS(id_token) 플로우(/login/google/*) 유지 — 이 라우트 미사용. */ interface AuthorizeUrlResponse { provider: string; authorizeUrl: string; } export async function GET(request: NextRequest, { params }: { params: Promise<{ provider: string }> }) { const { provider } = await params; const failRedirect = (message: string) => { const url = new URL('/login', request.url); url.searchParams.set('error', message); return NextResponse.redirect(url); }; if (!isSocialLoginProvider(provider)) { return failRedirect('지원하지 않는 소셜 로그인입니다.'); } const label = SOCIAL_PROVIDER_LABELS[provider]; const state = crypto.randomUUID(); const redirectUri = `${request.nextUrl.origin}/login/${provider}/callback`; const query = new URLSearchParams({ state, redirectUri }); const res = await fetchJson(`/api/auth/${provider}/authorize-url?${query.toString()}`); if (!res.success || !res.data?.authorizeUrl) { // OAuth 앱 키 미설정(Auth.SocialNotConfigured)은 사용자 친화 문구로 치환 if (res.errors?.some(e => e.code === 'Auth.SocialNotConfigured')) { return failRedirect(`${label} 로그인이 아직 준비되지 않았습니다. 잠시 후 다시 시도해주세요.`); } return failRedirect(res.errors?.[0]?.description || res.message || `${label} 로그인을 시작할 수 없습니다. 잠시 후 다시 시도해주세요.`); } const response = NextResponse.redirect(res.data.authorizeUrl); response.cookies.set(`${OAUTH_STATE_COOKIE_PREFIX}${provider}`, state, { httpOnly: true, path: '/', sameSite: 'lax', secure: process.env.NODE_ENV === 'production', maxAge: 600 }); return response; }